using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;

namespace Hsenl.Network {
    public abstract class Receiver : EpibolySocket, IReceiver {
        public Receiver(Socket socket) : base(socket) { }

        public int TcpRecvBufferSizePer { get; set; } = 1024 * 4;
        public int UdpRecvBufferSizePer { get; set; } = 1500;


        private volatile int _isRecving;
        private SocketBufferKind _bufferKind; // 有时候如果对于memory支持不友好的环境时, 可以使用array. 例如unity中, 对于memory的支持就不友好, 所以这只是权宜之计, 才增加了对byte[]的支持.
        private EndPoint RemoteEndPoint = new IPEndPoint(IPAddress.Any, 0);

        public bool IsRecving => this._isRecving != 0;
        public SocketBufferKind BufferKind => this._bufferKind;

        protected Receiver(Socket socket, SocketBufferKind bufferKind) : base(socket) {
            this.Init(socket, bufferKind);
        }

        public void Init(Socket socket, SocketBufferKind bufferKind) {
            base.Init(socket);
            this._bufferKind = bufferKind;
        }

        // 以下几种接收方案都能接收到数据, 不局限于发送方使用的是那种发送方案
        protected override bool RecvAsync() {
            this.CheckDisposedException();

            if (this.CompareExchange()) {
                this.ReceiveAsync().Tail();
                return true;
            }

            return false;
        }

        // 接收的同时, 能够拿到对方的地址
        protected override bool RecvFromAsync() {
            this.CheckDisposedException();

            if (this.CompareExchange()) {
                this.ReceiveFromAsync().Tail();
                return true;
            }

            return false;
        }

        // 不仅能拿到地址, 还能拿到IP头部信息和SocketFlags
        protected override bool RecvMessageFromAsync() {
            this.CheckDisposedException();

            if (this.CompareExchange()) {
                this.ReceiveMessageFromAsync().Tail();
                return true;
            }

            return false;
        }

        // 接收数据
        private async hvoid ReceiveAsync() {
            try {
                while (true) {
                    if (this.IsClosedOrDisposed()) {
                        return;
                    }

                    try {
                        switch (this.BufferKind) {
                            case SocketBufferKind.Memory: {
                                var memory = this.GetRecvBuffer(this.TcpRecvBufferSizePer);
                                if (memory.Length == 0) {
                                    this.StopRecving();
                                    return;
                                }

                                var len = await this.Socket.ReceiveAsync(memory);
                                this.OnRecv(memory.Slice(0, len));
                                break;
                            }
                            case SocketBufferKind.Array: {
                                var bytes = this.GetRecvBuffer(this.TcpRecvBufferSizePer, out var offset, out var count);
                                if (bytes.Length == 0) {
                                    this.StopRecving();
                                    return;
                                }

                                var len = await this.Socket.ReceiveAsync(bytes.AsMemory(offset, count));
                                this.OnRecv(bytes, offset, len);
                                break;
                            }
                            default:
                                throw new ArgumentOutOfRangeException();
                        }
                    }
                    catch (SocketException exception) {
                        this.Error(exception.ErrorCode);
                    }
                    catch (Exception exception) {
                        Log.Error(exception);
                    }
                }
            }
            finally {
                this.StopRecving();
            }
        }

        // 接收数据from
        private async hvoid ReceiveFromAsync() {
            try {
                while (true) {
                    if (this.IsClosedOrDisposed()) {
                        return;
                    }

                    try {
                        switch (this.BufferKind) {
                            case SocketBufferKind.Memory: {
                                var memory = this.GetRecvBuffer(this.UdpRecvBufferSizePer);
                                if (memory.Length == 0) {
                                    this.StopRecving();
                                    return;
                                }

                                var result = await this.Socket.ReceiveFromAsync(memory, this.RemoteEndPoint);
                                this.OnRecvFrom(result.RemoteEndPoint, memory.Slice(0, result.ReceivedBytes));
                                break;
                            }
                            case SocketBufferKind.Array: {
                                var bytes = this.GetRecvBuffer(this.UdpRecvBufferSizePer, out var offset, out var count);
                                if (bytes.Length == 0) {
                                    this.StopRecving();
                                    return;
                                }

                                var result = await this.Socket.ReceiveFromAsync(bytes.AsMemory(offset, count), this.RemoteEndPoint);
                                this.OnRecvFrom(result.RemoteEndPoint, bytes, offset, result.ReceivedBytes);
                                break;
                            }
                            default:
                                throw new ArgumentOutOfRangeException();
                        }
                    }
                    catch (SocketException exception) {
                        this.Error(exception.ErrorCode);
                    }
                    catch (Exception exception) {
                        Log.Error(exception);
                    }
                }
            }
            finally {
                this.StopRecving();
            }
        }

        // 接收数据message from
        private async hvoid ReceiveMessageFromAsync() {
            try {
                while (true) {
                    if (this.IsClosedOrDisposed()) {
                        return;
                    }

                    try {
                        switch (this.BufferKind) {
                            case SocketBufferKind.Memory: {
                                var memory = this.GetRecvBuffer(this.UdpRecvBufferSizePer);
                                if (memory.Length == 0) {
                                    this.StopRecving();
                                    return;
                                }

                                var result = await this.Socket.ReceiveMessageFromAsync(memory, this.RemoteEndPoint);
                                this.OnRecvMessageFrom(result.RemoteEndPoint, result.PacketInformation, result.SocketFlags,
                                    memory.Slice(0, result.ReceivedBytes));
                                break;
                            }
                            case SocketBufferKind.Array: {
                                var bytes = this.GetRecvBuffer(this.UdpRecvBufferSizePer, out var offset, out var count);
                                if (bytes.Length == 0) {
                                    this.StopRecving();
                                    return;
                                }

                                var result = await this.Socket.ReceiveMessageFromAsync(bytes.AsMemory(offset, count), this.RemoteEndPoint);
                                this.OnRecvMessageFrom(result.RemoteEndPoint, result.PacketInformation, result.SocketFlags,
                                    bytes, offset, result.ReceivedBytes);
                                break;
                            }
                            default:
                                throw new ArgumentOutOfRangeException();
                        }
                    }
                    catch (SocketException exception) {
                        this.Error(exception.ErrorCode);
                    }
                    catch (Exception exception) {
                        Log.Error(exception);
                    }
                }
            }
            finally {
                this.StopRecving();
            }
        }

        protected abstract Memory<byte> GetRecvBuffer(int length);
        protected abstract byte[] GetRecvBuffer(int length, out int offset, out int count);

        protected virtual void OnRecv(Memory<byte> data) { }
        protected virtual void OnRecv(byte[] data, int offset, int count) { }

        protected virtual void OnRecvFrom(EndPoint remoteEndPoint, Memory<byte> data) { }
        protected virtual void OnRecvFrom(EndPoint remoteEndPoint, byte[] data, int offset, int count) { }

        protected virtual void OnRecvMessageFrom(EndPoint remoteEndPoint, IPPacketInformation information, SocketFlags socketFlags, Memory<byte> data) { }

        protected virtual void OnRecvMessageFrom(EndPoint remoteEndPoint, IPPacketInformation information, SocketFlags socketFlags, byte[] data, int offset,
            int count) { }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private bool CompareExchange() {
            return Interlocked.CompareExchange(ref this._isRecving, 1, 0) == 0;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void StopRecving() {
            Interlocked.Exchange(ref this._isRecving, 0);
            // 我们无法直接的关闭 saea, 需要等到他 completed, 才算是关闭了
            if (this.IsClosed) {
                this.OnClosed();
            }
        }

        public override void Close(bool closeSocket = true) {
            if (this.IsClosed)
                return;

            base.Close(closeSocket);

            if (!this.IsRecving) {
                this.OnClosed();
            }
        }

        public override void Dispose() {
            if (this.IsDisposed)
                return;

            base.Dispose();
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private bool IsClosedOrDisposed() {
            return this.IsClosed || this.IsDisposed;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void CheckGreaterThanTcpRecvSizeException(int len) {
            if (len > this.TcpRecvBufferSizePer)
                throw new ArgumentOutOfRangeException($"RecvBufferLength over maximum '{len}' '{this.TcpRecvBufferSizePer}'");
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void CheckGreaterThanUdpRecvSizeException(int len) {
            if (len > this.UdpRecvBufferSizePer)
                throw new ArgumentOutOfRangeException($"RecvBufferLength over maximum '{len}' '{this.UdpRecvBufferSizePer}'");
        }
    }
}